namespace YogoPop.Component.Cache;

[DIModeForService(DIModeEnum.AsSelf)]
public class FRedisProvider : ISingleton
{
    private readonly object _lock = new();
    private RedisClient _client { get; set; }

    public RedisClient GetClient()
    {
        var redisSettings = InjectionContext.Resolve<RedisSettings>();
        if (redisSettings == null) return default;
        if (redisSettings.ConnectionList.IsEmpty()) return default;

        var options = redisSettings.ConnectionList.Select(o =>
        {
            var option = new ConnectionStringBuilder
            {
                Host = o,
                Database = redisSettings.DBIndex,
                IdleTimeout = TimeSpan.FromSeconds(redisSettings.IdelTimeout),
                ConnectTimeout = TimeSpan.FromSeconds(30),
                SendTimeout = TimeSpan.FromSeconds(30),
                ReceiveTimeout = TimeSpan.FromSeconds(30),
            };

            if (redisSettings.PoolSize != default)
            {
                option.MaxPoolSize = redisSettings.PoolSize;
                option.MinPoolSize = (int)(redisSettings.PoolSize * 0.6);
            }

            return option;

        }).ToArray();
        if (options.IsEmpty()) return default;

        if (redisSettings.PoolSize == default)
        {
            return options.Length == 1 ? new RedisClient(options[0]) : new RedisClient(options);
        }
        else
        {
            lock (_lock)
            {
                if (_client != null) return _client;
                _client = options.Length == 1 ? new RedisClient(options[0]) : new RedisClient(options);
                return _client;
            }
        }
    }
}

public class FRedis : RedisBasic
{
    public FRedis(RedisSettings redisSettings, int defaultDatabase = -1) : base(redisSettings, defaultDatabase) { }

    private RedisClient _client { get; set; }

    public override void DisposeClient() { if (_client != null) { _client.Dispose(); _client = null; } }

    private RedisClient getClient(int? dbIndex = null)
    {
        var result = default(RedisClient);

        if (RedisSettings.PoolSize == default)
        {
            if (_client == null) _client = InjectionContext.Resolve<FRedisProvider>().GetClient();
            result = _client;
        }
        else
        {
            result = InjectionContext.Resolve<FRedisProvider>().GetClient();
        }

        return result.GetDatabase(dbIndex.HasValue ? dbIndex.Value : CurrentDatabase);
    }

    public override T GetClient<T>(int? dbIndex = null) => getClient(dbIndex).Convert<T>();

    public override List<string> Keys(string pattern = "*", int? dbIndex = null)
    {
        var result = new List<string>();
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var keys = db.Keys(pattern);
            if (keys.IsEmpty())
                return result;

            result = keys.ToList();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override List<T> List<T>(string pattern = "*", int? dbIndex = null)
    {
        var result = new List<T>();
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var keys = db.Keys(pattern);
            if (keys.IsEmpty())
                return result;

            keys.ToList().ForEach(o => { result.Add(db.Get<T>(o)); });
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool Expire(string key, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.Expire(key, 0);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool Expire(string key, TimeSpan ts, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.Expire(key, ts);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool ListRightPush(string key, string obj, DateTime? expiredTime = null, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            key = FillKey(key);

            result = db.RPush(key, obj) == 1;

            if (expiredTime.HasValue)
                db.ExpireAt(key, expiredTime.Value);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool ListRemove(string key, string obj, long count = 0, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.LRem(FillKey(key), count, obj) != default;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override List<T> ListRange<T>(string key, int? dbIndex = null)
    {
        var result = new List<T>();
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var values = db.LRange(FillKey(key), 0, -1);
            foreach (var value in values)
            {
                var item = value.IsEmptyString() ? default : (typeof(T).IsString() ? value.Convert<T>() : value.ToObject<T>());
                result.Add(item);
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool Set<T>(string key, T value, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            //if (value == null || value.ToString().IsEmptyString())
            //    throw new ArgumentNullException(nameof(value));

            var data = value is string ? value.ToString() : value.ToJson();

            db = getClient(dbIndex);
            if (db == null) return result;

            db.Set(FillKey(key), data);

            result = true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool Set<T>(string key, T value, TimeSpan ts, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            //if (value == null || value.ToString().IsEmptyString())
            //    throw new ArgumentNullException(nameof(value));

            var data = value is string ? value.ToString() : value.ToJson();

            db = getClient(dbIndex);
            if (db == null) return result;

            db.Set(FillKey(key), data, ts);

            result = true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool Del(string key, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.Del(FillKey(key)) != default;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override T Get<T>(string key, int? dbIndex = null)
    {
        var result = default(T);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var value = db.Get(FillKey(key));

            result = value.IsEmptyString() ? default(T) : (typeof(T).IsString() ? value.Convert<T>() : value.ToObject<T>());
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool Exists(string key, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.Exists(FillKey(key));
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override long Increase(string key, long increment = 1, int? dbIndex = null)
    {
        var result = default(long);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.IncrBy(FillKey(key), increment);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override long Decrease(string key, long decrement = 1, int? dbIndex = null)
    {
        var result = default(long);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.IncrBy(FillKey(key), MathHelper.ToNegative(decrement).Convert<long>());
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool HSet<T>(string key, string field, T value, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            var data = value is string ? value.ToString() : value.ToJson();

            db = getClient(dbIndex);
            if (db == null) return result;

            db.HSet(FillKey(key), field, data);

            result = true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool HSet<T>(string key, Dictionary<string, T> fields, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            var data = fields.ToDictionary(
                field => field.Key,
                field => field.Value is string ? field.Value.ToString() : field.Value.ToJson()
            );

            db = getClient(dbIndex);
            if (db == null) return result;

            db.HMSet(FillKey(key), data);

            result = true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override long HDel(string key, int? dbIndex = null, params string[] fields)
    {
        var result = default(long);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.HDel(FillKey(key), fields);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override T HGet<T>(string key, string field, int? dbIndex = null)
    {
        var result = default(T);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var value = db.HGet(FillKey(key), field);

            result = value.IsEmptyString() ? default(T) : (typeof(T).IsString() ? value.Convert<T>() : value.ToObject<T>());
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override Dictionary<string, T> HGet<T>(string key, int? dbIndex = null)
    {
        var result = new Dictionary<string, T>();
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var dic = db.HGetAll(FillKey(key));
            if (dic.IsEmpty())
                return result;

            foreach (var kv in dic)
            {
                var field = kv.Key;
                var value = kv.Value;

                result[field] = value.IsEmptyString() ? default(T) : (typeof(T).IsString() ? value.Convert<T>() : value.ToObject<T>());
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override Dictionary<string, T> HGet<T>(string key, int? dbIndex = null, params string[] fields)
    {
        var result = new Dictionary<string, T>();
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            var values = db.HMGet(FillKey(key), fields);
            if (values.IsEmpty())
                return result;

            for (int i = 0; i < fields.Length; i++)
            {
                var field = fields[i];
                var value = values[i];

                result[field] = value.IsEmptyString() ? default(T) : (typeof(T).IsString() ? value.Convert<T>() : value.ToObject<T>());
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override bool HExists(string key, string field, int? dbIndex = null)
    {
        var result = default(bool);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.HExists(FillKey(key), field);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override long HIncrease(string key, string field, long increment = 1, int? dbIndex = null)
    {
        var result = default(long);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.HIncrBy(FillKey(key), field, increment);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }

    public override long HDecrease(string key, string field, long decrement = 1, int? dbIndex = null)
    {
        var result = default(long);
        var db = default(RedisClient);

        try
        {
            db = getClient(dbIndex);
            if (db == null) return result;

            result = db.HIncrBy(FillKey(key), field, MathHelper.ToNegative(decrement).Convert<long>());
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            //if (db != null) db.Dispose();
        }

        return result;
    }
}